package simplenote

import (
	"bytes"
	"code.google.com/p/rsc/keychain"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"strings"
	"strconv"
	"time"
)

const (
	loginUrl = "https://simple-note.appspot.com/api/login"
	indexUrl = "https://simple-note.appspot.com/api2/index"
	dataUrl  = "https://simple-note.appspot.com/api2/data"
)

/*
   "modifydate": "1301271271.578980",
   "tags": [],
   "deleted": 0,
   "createdate": "1273799403.000000",
   "systemtags": [],
   "version": 9,
   "syncnum": 10,
   "key": "agtzaW1wbGUtbm90ZXINCxIETm90ZRjWreYBDA",
   "minversion": 1
*/

type IndexEnt struct {
	Modifydate string   `json:"modifydate,omitempty"`
	Createdate string   `json:"createdate,omitempty"`
	Syncnum    int      `json:"syncnum,omitempty"`
	Version    int      `json:"version,omitempty"`
	Tags       []string `json:"tags,omitempty"`
	Key        string   `json:"key,omitempty"`
}

type indexResp struct {
	Count int         `json:"count"`
	Data  []*IndexEnt `json:"data"`
	Time  string      `json:"time"`
	Mark  *string     `json:"mark"`
}

/*

  "modifydate": "1301271275.349665",
  "tags": [],
  "deleted": 0,
  "createdate": "1272850757.000000",
  "systemtags": [],
  "content":
    "lorem ipsum",
  "version": 8,
  "syncnum": 9,
  "key": "agtzaW1wbGUtbm90ZXINCxIETm90ZRjy1s8BDA",
  "minversion": 1

*/
type Note struct {
	Modifydate string   `json:"modifydate,omitempty"`
	Tags       []string `json:"tags,omitempty"`
	Deleted    int      `json:"deleted,omitempty"`
	Createdate string   `json:"createdate,omitempty"`
	Content    string   `json:"content,omitempty"`
	Version    int      `json:"version,omitempty"`
	Syncnum    int      `json:"syncnum,omitempty"`
	Key        string   `json:"key,omitempty"`
	Minversion int      `json:"minversion,omitempty"`
}

type Simplenote struct {
	token   string
	email   string
	timeout time.Duration

	client *http.Client
}

// 10 second timeout
func Login() (*Simplenote, error) {
	return LoginTimeout(10 * time.Second)
}

func LoginTimeout(timeout time.Duration) (*Simplenote, error) {
	dialer := func(network, addr string) (net.Conn, error) {
		conn, err := net.DialTimeout(network, addr, timeout)
		if err != nil {
			return nil, err
		}

		return &timeoutConn{conn, timeout}, nil
	}

	client := &http.Client{Transport: &http.Transport{Dial: dialer}}

	email, password, err := keychain.UserPasswd("simple-note.appspot.com", "")
	if err != nil {
		return nil, err
	}
	body := fmt.Sprintf("email=%s&password=%s", email, password)
	body = base64.URLEncoding.EncodeToString([]byte(body))

	resp, err := client.Post(loginUrl, "text/plain", bytes.NewBufferString(body))
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, errors.New(resp.Status)
	}

	respbytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return &Simplenote{string(respbytes), email, timeout, client}, nil
}

func (sn *Simplenote) indexPage(mark *string) (*indexResp, error) {
	url := fmt.Sprintf("%s?auth=%s&email=%s&length=100", indexUrl, sn.token, sn.email)
	if mark != nil {
		url += "&mark=" + *mark
	}
	resp, err := sn.client.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, errors.New(resp.Status)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	var indexResp indexResp
	err = json.Unmarshal(body, &indexResp)
	if err != nil {
		return nil, err
	}

	return &indexResp, nil
}

func (sn *Simplenote) Index() ([]*IndexEnt, error) {
	var mark *string
	var ents []*IndexEnt

	for {
		resp, err := sn.indexPage(mark)
		if err != nil {
			return nil, err
		}

		ents = append(ents, resp.Data...)

		mark = resp.Mark
		if mark == nil {
			return ents, nil
		}
	}

	panic("notreached")
}

func (sn *Simplenote) Note(key string) (*Note, error) {
	resp, err := sn.client.Get(fmt.Sprintf("%s/%s?auth=%s&email=%s", dataUrl, key, sn.token, sn.email))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, errors.New(resp.Status)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	note := new(Note)
	err = json.Unmarshal(body, note)
	if err != nil {
		return nil, err
	}

	return note, nil
}

func (sn *Simplenote) Save(note *Note) (*Note, error) {
	body, err := json.Marshal(note)
	if err != nil {
		return nil, err
	}

	escaped := url.QueryEscape(string(body))
	reader := bytes.NewBufferString(escaped)

	var url string
	if note.Key != "" {
		url = fmt.Sprintf("%s/%s?auth=%s&email=%s", dataUrl, note.Key, sn.token, sn.email)
	} else {
		url = fmt.Sprintf("%s?auth=%s&email=%s", dataUrl, sn.token, sn.email)
	}

	resp, err := sn.client.Post(url, "text/plain", reader)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode >= 400 {
		return nil, errors.New(fmt.Sprintf("HTTP status %d while attempting to POST %s", resp.StatusCode, url))
	}

	defer resp.Body.Close()
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	newNote := new(Note)
	err = json.Unmarshal(body, newNote)
	if err != nil {
		return nil, err
	}

	return newNote, nil
}

func (N *Note) TitleBody() (string, string) {
	split := strings.SplitN(N.Content, "\n", 2)
	switch len(split) {
	case 0:
		return "", ""
	case 1:
		return split[0], ""
	case 2:
		return split[0], split[1]
	}
	panic("notreached")
}

func (N* Note) ModTime() time.Time {
	utime, err := strconv.ParseFloat(N.Modifydate, 32)
	if err != nil {
		return time.Unix(0, 0)
	}
	
	return time.Unix(int64(utime), 0)
}

func (N* Note) DebugString() string {
	bytes, err := json.MarshalIndent(N, "", "\t")
	if err != nil {
		return "<error>"
	}
	
	return string(bytes)
}

// todo: ShortKey()  [sha1] take 8